package com.vrp3d.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.vrp3d.common.context.InvokerContext;
import com.vrp3d.common.enums.CommonEnum;
import com.vrp3d.common.enums.UserEnum;
import com.vrp3d.common.enums.UserExceptionEnum;
import com.vrp3d.common.exceptions.UserException;
import com.vrp3d.common.response.RestResponse;
import com.vrp3d.common.utils.*;
import com.vrp3d.domain.dto.user.UserForChangePasswordDTO;
import com.vrp3d.domain.dto.user.UserForLoginDTO;
import com.vrp3d.domain.dto.user.UserForRecoverPasswordDTO;
import com.vrp3d.domain.dto.user.UserForRegisteredDTO;
import com.vrp3d.domain.model.UserContext;
import com.vrp3d.domain.po.User;
import com.vrp3d.domain.po.UserExample;
import com.vrp3d.mapper.UserMapper;
import com.vrp3d.service.RedisService;
import com.vrp3d.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
import java.util.List;

/**
 * 用户
 *
 * @author vrp3d
 */
@Slf4j
@Service
public class UserServiceImpl extends AbsServiceImpl implements UserService {

    @Value("${ali.registeredTemplateCode}")
    private String registeredTemplateCode;
    @Value("${ali.recoverPasswordTemplateCode}")
    private String recoverPasswordTemplateCode;

    @Autowired
    private RedisService redisService;

    @Autowired
    private UserMapper userMapper;

    @Override
    public User selectByPrimaryKey(Integer id) {
        UserExample example = new UserExample();
        UserExample.Criteria criteria = example.createCriteria();
        criteria.andIdEqualTo(id);
        criteria.andStatusEqualTo(UserEnum.NORMAL.getMsg());
        List<User> list = userMapper.selectByExample(example);
        return CollectionUtils.isEmpty(list) ? null : list.get(0);
    }

    @Override
    public User findByMobile(String mobile) {
        UserExample example = new UserExample();
        UserExample.Criteria criteria = example.createCriteria();
        criteria.andMobileEqualTo(mobile.trim());
        criteria.andStatusEqualTo(UserEnum.NORMAL.getMsg());
        List<User> list = userMapper.selectByExample(example);
        return CollectionUtils.isEmpty(list) ? null : list.get(0);
    }

    @Override
    public User findByEmail(String email) {
        UserExample example = new UserExample();
        UserExample.Criteria criteria = example.createCriteria();
        criteria.andStatusEqualTo(UserEnum.NORMAL.getMsg());
        criteria.andEmailEqualTo(email.trim());
        List<User> list = userMapper.selectByExample(example);
        return CollectionUtils.isEmpty(list) ? null : list.get(0);
    }

    @Override
    public User findByUsername(String username) {
        UserExample example = new UserExample();
        UserExample.Criteria criteria = example.createCriteria();
        criteria.andStatusEqualTo(UserEnum.NORMAL.getMsg());
        criteria.andUsernameEqualTo(username.trim());
        List<User> list = userMapper.selectByExample(example);
        return CollectionUtils.isEmpty(list) ? null : list.get(0);
    }

    @Override
    public String login(UserForLoginDTO user, HttpServletRequest request, HttpServletResponse response) {
        String username = user.getUsername();
        String password = user.getPassword();
        User userFromMysql = findByMobile(username);
        if (null == userFromMysql) {
            userFromMysql = findByEmail(username);
            if (null == userFromMysql) {
                log.info("用户不存在:{}", username);
                throw new UserException(UserExceptionEnum.USER_DOES_NOT_EXIST);
            }
        }
        password = PasswordEncoderUtil.passwordHash(password, userFromMysql.getSalt());
        UsernamePasswordToken upToken = new UsernamePasswordToken(username, password);
        Subject subject = SecurityUtils.getSubject();
        if (subject == null) {
            throw new UserException(UserExceptionEnum.LOGIN_FAILED);
        }
        try {
            subject.login(upToken);
            String token = JwtUtil.createJwt(username, userFromMysql.getId());

            // 处理用户信息
            handleUserInfo(request, response, userFromMysql);

            // 用redis做全局唯一token
//            redisService.putCacheWithExpireTime(CommonEnum.USER_TOKEN.getMsg() + userFromMysql.getId(), token, DateUtil.howManySecondsUntilMidnight().intValue());
            return token;
        } catch (IncorrectCredentialsException e) {
            log.error("密码错误" + e);
            throw new UserException(UserExceptionEnum.WRONG_PASSWORD);
        }
    }

    /**
     * 处理用户信息
     *
     * @param request
     * @param response
     * @param userFromMysql
     */
    private void handleUserInfo(HttpServletRequest request, HttpServletResponse response, User userFromMysql) {
        // 用户上下文中存放用户信息
        UserContext userContext = new UserContext();
        BeanUtils.copyProperties(userFromMysql, userContext);
        InvokerContext.setUserCtx(userContext);

        // 用户信息存入cookie中
        String jsonString = JSONObject.toJSONString(userContext);
        CookieUtil.setCookie(request, response, CommonEnum.COOKIE_USER_INFO.getMsg(), jsonString, DateUtil.howManySecondsUntilMidnight().intValue(), CommonEnum.CODING_TYPE.getMsg());
    }

    @Override
    public void logout() {
        // 删除redis
        UserContext user = getUser();
        redisService.remove(CommonEnum.USER_TOKEN.getMsg() + user.getId());
        // 删除上下文信息
        InvokerContext.clear();
        // 登出系统
        SecurityUtils.getSubject().logout();
    }

    @Override
    public void registered(UserForRegisteredDTO user) {
        // 验证手机号是否已经被绑定
        String mobile = user.getMobile();
        User findUserByMobile = findByMobile(mobile);
        if (ObjUtil.isNotEmpty(findUserByMobile)) {
            throw new UserException(UserExceptionEnum.USER_ALREADY_EXISTS);
        }

        // 校验验证码
        String captcha = user.getCaptcha();
        String captchaFromRedis = redisService.query(CommonEnum.REGISTERED_VERIFICATION_CODE.getMsg() + mobile);
        if (StringUtils.isNotBlank(captchaFromRedis)) {
            if (!captcha.equals(captchaFromRedis)) {
                throw new UserException(UserExceptionEnum.INCORRECT_VERIFICATION_CODE);
            } else {
                // 密码在注解已经校验,直接加盐加密即可
                String salt = PasswordEncoderUtil.generateSalt();
                String password = user.getPassword();
                password = PasswordEncoderUtil.passwordHash(password, salt);
                user.setPassword(password);

                // 插入数据库
                User insert = new User();
                BeanUtils.copyProperties(user, insert);
                insert.setSalt(salt);

                // 设置用户状态
                insert.setStatus(UserEnum.NORMAL.getMsg());

                Date date = getNowTime();
                insert.setCreatedAt(date);
                insert.setUpdatedAt(date);

                userMapper.insertSelective(insert);

                // 删除redis中验证码
                redisService.remove(CommonEnum.REGISTERED_VERIFICATION_CODE.getMsg() + mobile);
            }
        } else {
            throw new UserException(UserExceptionEnum.VERIFICATION_CODE_HAS_EXPIRED);
        }
    }

    @Override
    public void getPhoneVerificationCodeWhenRegistering(String mobile) {
        // 验证该手机是否已经是该平台用户
        User userByMobile = findByMobile(mobile);
        if (ObjUtil.isNotEmpty(userByMobile)) {
            throw new UserException(UserExceptionEnum.USER_ALREADY_EXISTS);
        } else {
            // 验证是否能发送
            verifyPhoneVerificationCodeCanItBeSent(mobile, CommonEnum.REGISTERED_ONE_MINUTE_ONE_MESSAGE, CommonEnum.REGISTERED_ONE_HOUR_FIVE_MESSAGE, CommonEnum.REGISTERED_ONE_DAY_TEN_MESSAGE);
            // 验证通过,发送验证码,5分钟有效,并将验证码存放到redis中
            String validateCode = RandomUtil.generateNumberString(CommonEnum.NUMBER_OF_RECOVER_PASSWORD_VERIFICATION_CODE.getMsgInteger());
            redisService.putCacheWithExpireTime(CommonEnum.REGISTERED_VERIFICATION_CODE.getMsg() + mobile, validateCode, CommonEnum.REGISTERED_VERIFICATION_CODE.getMsgInteger());
            // 发送短信
            boolean sendResult = AliSmsUtil.send(mobile, validateCode, registeredTemplateCode);
            if (!sendResult) {
                throw new UserException(UserExceptionEnum.MESSAGE_FAILED_TO_SEND);
            }
            // 发送成功后记录
            recordAfterSendingVerificationCode(mobile, CommonEnum.REGISTERED_ONE_MINUTE_ONE_MESSAGE, CommonEnum.REGISTERED_ONE_HOUR_FIVE_MESSAGE, CommonEnum.REGISTERED_ONE_DAY_TEN_MESSAGE);
        }
    }

    @Override
    public RestResponse getRecoverPasswordVerificationCode(String mobile) {
        // 验证是否为本平台用户
        User userByMobile = findByMobile(mobile);
        if (ObjUtil.isNotEmpty(userByMobile)) {
            // 验证是否允许发送
            verifyPhoneVerificationCodeCanItBeSent(mobile, CommonEnum.RECOVER_PASSWORD_ONE_MINUTE_ONE_MESSAGE, CommonEnum.RECOVER_PASSWORD_ONE_HOUR_FIVE_MESSAGE, CommonEnum.RECOVER_PASSWORD_ONE_DAY_TEN_MESSAGE);
            // 验证通过发送验证码,5分种内有效
            String validateCode = RandomUtil.generateNumberString(CommonEnum.NUMBER_OF_RECOVER_PASSWORD_VERIFICATION_CODE.getMsgInteger());
            redisService.putCacheWithExpireTime(CommonEnum.RECOVER_PASSWORD_VERIFICATION_CODE.getMsg() + mobile, validateCode, CommonEnum.RECOVER_PASSWORD_VERIFICATION_CODE.getMsgInteger());
            // 发送短信
            boolean sendResult = AliSmsUtil.send(mobile, validateCode, recoverPasswordTemplateCode);
            if (!sendResult) {
                throw new UserException(UserExceptionEnum.MESSAGE_FAILED_TO_SEND);
            }
            // 发送成功后记录
            recordAfterSendingVerificationCode(mobile, CommonEnum.RECOVER_PASSWORD_ONE_MINUTE_ONE_MESSAGE, CommonEnum.RECOVER_PASSWORD_ONE_HOUR_FIVE_MESSAGE, CommonEnum.RECOVER_PASSWORD_ONE_DAY_TEN_MESSAGE);
            return new RestResponse(UserExceptionEnum.VERIFICATION_CODE_SENT_SUCCESSFULLY.getMsg());
        } else {
            throw new UserException(UserExceptionEnum.USER_DOES_NOT_EXIST);
        }
    }

    @Override
    public void recoverPassword(UserForRecoverPasswordDTO user) {
        String mobile = user.getMobile();
        String captcha = user.getCaptcha();
        // 验证是否本平台用户
        User oldUser = findByMobile(mobile);
        if (ObjUtil.isNotEmpty(oldUser)) {
            // 校验验证码
            String captchaFromRedis = redisService.query(CommonEnum.RECOVER_PASSWORD_VERIFICATION_CODE.getMsg() + mobile);
            if (StringUtils.isNotBlank(captchaFromRedis)) {
                if (captcha.equals(captchaFromRedis)) {
                    String password = user.getPassword();

                    // 生成新的密码
                    password = PasswordEncoderUtil.passwordHash(password, oldUser.getSalt());
                    User update = new User();
                    update.setId(oldUser.getId());
                    update.setPassword(password);
                    userMapper.updateByPrimaryKeySelective(update);

                    // 删除验证码
                    redisService.remove(CommonEnum.RECOVER_PASSWORD_VERIFICATION_CODE.getMsg() + mobile);
                } else {
                    throw new UserException(UserExceptionEnum.INCORRECT_VERIFICATION_CODE);
                }
            } else {
                throw new UserException(UserExceptionEnum.VERIFICATION_CODE_HAS_EXPIRED);
            }
        } else {
            throw new UserException(UserExceptionEnum.USER_DOES_NOT_EXIST);
        }
    }

    @Override
    public void changePassword(UserForChangePasswordDTO user) {
        // 查询数据库是否本平台用户
        UserContext userContext = getUser();
        Integer id = userContext.getId();
        User userFromMysql = selectByPrimaryKey(id);
        if (ObjUtil.isEmpty(userFromMysql)) {
            throw new UserException(UserExceptionEnum.USER_DOES_NOT_EXIST);
        }

        // 原密码是否正确
        String oldPassword = user.getOldPassword();
        String salt = userFromMysql.getSalt();
        oldPassword = PasswordEncoderUtil.passwordHash(oldPassword, salt);
        if (!oldPassword.equals(userFromMysql.getPassword())) {
            throw new UserException(UserExceptionEnum.THE_ORIGINAL_PASSWORD_IS_INCORRECT);
        }

        // 两次密码是否一致
        String newPassword = user.getNewPassword();
        String confirmPassword = user.getConfirmPassword();
        if (!newPassword.equals(confirmPassword)) {
            throw new UserException(UserExceptionEnum.PASSWORDS_ENTERED_TWICE_ARE_INCONSISTENT);
        }

        // 新密码是否符合规范
        if (!PasswordEncoderUtil.checkPwd(newPassword)) {
            throw new UserException(UserExceptionEnum.PASSWORD_DOES_NOT_MEET_SPECIFICATIONS);
        }

        // 新旧密码是否一致(不能一致)
        if (oldPassword.equals(newPassword)) {
            throw new UserException(UserExceptionEnum.NEW_AND_OLD_PASSWORD_CAN_NOT_BE_THE_SAME);
        }

        // 修改数据库(加盐加密)
        User update = new User();
        update.setId(id);
        newPassword = PasswordEncoderUtil.passwordHash(newPassword, salt);
        update.setPassword(newPassword);
        userMapper.updateByPrimaryKeySelective(update);

        // 删除token
        redisService.remove(CommonEnum.USER_TOKEN.getMsg() + id);

        // 删除用户上下文
        InvokerContext.clear();

        // 退出登录
        Subject subject = SecurityUtils.getSubject();
        subject.logout();
    }

}
